/*
 * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by the
 * Free Software Foundation; either version 3 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "CreatureScript.h"
#include "GridNotifiers.h"
#include "PassiveAI.h"
#include "ScriptedCreature.h"
#include "Spell.h"
#include "SpellAuraEffects.h"
#include "SpellScript.h"
#include "SpellScriptLoader.h"
#include "TaskScheduler.h"

//
//  Emerald Dragon NPCs and IDs (kept here for reference)
//

enum EmeraldDragonNPC
{
    NPC_DREAM_FOG                   = 15224,
    DRAGON_YSONDRE                  = 14887,
    DRAGON_LETHON                   = 14888,
    DRAGON_EMERISS                  = 14889,
    DRAGON_TAERAR                   = 14890,

    GUID_DRAGON                     = 1,
    GUID_FOG_TARGET                 = 2
};

//
// Emerald Dragon Spells (used for the dragons)
//

enum EmeraldDragonSpells
{
    SPELL_TAIL_SWEEP                = 15847,    // tail sweep - slap everything behind dragon (2 seconds interval)
    SPELL_SUMMON_PLAYER             = 24776,    // teleport highest threat player in front of dragon if wandering off
    SPELL_DREAM_FOG                 = 24777,    // auraspell for Dream Fog NPC (15224)
    SPELL_SLEEP                     = 24778,    // sleep triggerspell (used for Dream Fog)
    SPELL_SEEPING_FOG_LEFT          = 24813,    // dream fog - summon left
    SPELL_SEEPING_FOG_RIGHT         = 24814,    // dream fog - summon right
    SPELL_NOXIOUS_BREATH            = 24818,
    SPELL_MARK_OF_NATURE            = 25040,    // Mark of Nature trigger (applied on target death - 15 minutes of being suspectible to Aura Of Nature)
    SPELL_MARK_OF_NATURE_AURA       = 25041,    // Mark of Nature (passive marker-test, ticks every 10 seconds from boss, triggers spellID 25042 (scripted)
    SPELL_AURA_OF_NATURE            = 25043,    // Stun for 2 minutes (used when SPELL_MARK_OF_NATURE exists on the target)
};

//
// Emerald Dragon Eventlists (shared and specials)
//

enum Events
{
    // General events for all dragons
    EVENT_SEEPING_FOG = 1,
    EVENT_NOXIOUS_BREATH,
    EVENT_TAIL_SWEEP,
    EVENT_SUMMON_PLAYER,

    // Ysondre
    EVENT_LIGHTNING_WAVE,
    EVENT_SUMMON_DRUID_SPIRITS,

    // Lethon
    EVENT_SHADOW_BOLT_WHIRL,

    // Emeriss
    EVENT_VOLATILE_INFECTION,
    EVENT_CORRUPTION_OF_EARTH,

    // Taerar
    EVENT_ARCANE_BLAST,
    EVENT_BELLOWING_ROAR,
};

/*
 * ---
 * --- Emerald Dragons : Base AI-structure used for all the Emerald dragons
 * ---
 */

struct emerald_dragonAI : public WorldBossAI
{
    emerald_dragonAI(Creature* creature) : WorldBossAI(creature)
    {
    }

    void Reset() override
    {
        WorldBossAI::Reset();
        me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_NON_ATTACKABLE);
        me->SetReactState(REACT_AGGRESSIVE);
        DoCast(me, SPELL_MARK_OF_NATURE_AURA, true);
        events.ScheduleEvent(EVENT_TAIL_SWEEP, 4000);
        events.ScheduleEvent(EVENT_NOXIOUS_BREATH, urand(7500, 15000));
        events.ScheduleEvent(EVENT_SEEPING_FOG, urand(12500, 20000));
        events.ScheduleEvent(EVENT_SUMMON_PLAYER, 1s);
    }

    // Target killed during encounter, mark them as suspectible for Aura Of Nature
    void KilledUnit(Unit* who) override
    {
        if (who->GetTypeId() == TYPEID_PLAYER)
            who->CastSpell(who, SPELL_MARK_OF_NATURE, true);
    }

    // Execute and reschedule base events shared between all Emerald Dragons
    void ExecuteEvent(uint32 eventId) override
    {
        switch (eventId)
        {
            case EVENT_SEEPING_FOG:
                // Seeping Fog appears only as "pairs", and only ONE pair at any given time!
                // Despawntime is 2 minutes, so reschedule it for new cast after 2 minutes + a minor "random time" (30 seconds at max)
                DoCast(me, SPELL_SEEPING_FOG_LEFT, true);
                DoCast(me, SPELL_SEEPING_FOG_RIGHT, true);
                events.ScheduleEvent(EVENT_SEEPING_FOG, urand(120000, 150000));
                break;
            case EVENT_NOXIOUS_BREATH:
                // Noxious Breath is cast on random intervals, no less than 7.5 seconds between
                DoCast(me, SPELL_NOXIOUS_BREATH);
                events.ScheduleEvent(EVENT_NOXIOUS_BREATH, urand(7500, 15000));
                break;
            case EVENT_TAIL_SWEEP:
                // Tail Sweep is cast every two seconds, no matter what goes on in front of the dragon
                DoCast(me, SPELL_TAIL_SWEEP);
                events.ScheduleEvent(EVENT_TAIL_SWEEP, 2000);
                break;
            case EVENT_SUMMON_PLAYER:
                if (Unit* target = me->GetVictim())
                    if (!target->IsWithinRange(me, 50.f))
                        DoCast(target, SPELL_SUMMON_PLAYER);
                events.ScheduleEvent(EVENT_SUMMON_PLAYER, 500ms);
                break;
        }
    }

    void JustSummoned(Creature* summon) override
    {
        if (summon->GetEntry() == NPC_DREAM_FOG)
        {
            summon->AI()->SetGUID(me->GetGUID(), GUID_DRAGON);
        }
    }

    void UpdateAI(uint32 diff) override
    {
        if (!UpdateVictim())
            return;

        events.Update(diff);

        if (me->HasUnitState(UNIT_STATE_CASTING))
            return;

        while (uint32 eventId = events.ExecuteEvent())
            ExecuteEvent(eventId);

        DoMeleeAttackIfReady();
    }
};

/*
 * --- NPC: Dream Fog
 */

class npc_dream_fog : public CreatureScript
{
public:
    npc_dream_fog() : CreatureScript("npc_dream_fog") { }

    struct npc_dream_fogAI : public ScriptedAI
    {
        npc_dream_fogAI(Creature* creature) : ScriptedAI(creature) { }

        void Reset() override
        {
            ScheduleEvents();
        }

        void ScheduleEvents()
        {
            scheduler.CancelAll();

            scheduler.Schedule(1s, [this](TaskContext context)
            {
                // Chase target, but don't attack - otherwise just roam around
                if (Unit* chaseTarget = GetRandomUnitFromDragonThreatList())
                {
                    me->GetMotionMaster()->Clear();
                    me->GetMotionMaster()->MoveFollow(chaseTarget, 0.02f, 0.0f);
                    _targetGUID = chaseTarget->GetGUID();
                    context.Repeat(15s, 30s);
                }
                else
                {
                    me->GetMotionMaster()->Clear();
                    me->GetMotionMaster()->MoveRandom(25.0f);
                    context.Repeat(2500ms);
                }

                // Seeping fog movement is slow enough for a player to be able to walk backwards and still outpace it
                me->SetWalk(true);
                me->SetSpeed(MOVE_WALK, 0.75f);
            });
        }

        void SetGUID(ObjectGuid guid, int32 type) override
        {
            if (type == GUID_DRAGON)
            {
                _dragonGUID = guid;
            }
            else if (type == GUID_FOG_TARGET)
            {
                if (guid == _targetGUID)
                {
                    ScheduleEvents();
                }
            }
        }

        Unit* GetRandomUnitFromDragonThreatList()
        {
            if (Creature* dragon = ObjectAccessor::GetCreature(*me, _dragonGUID))
            {
                if (dragon->GetAI())
                {
                    return dragon->GetAI()->SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true);
                }
            }

            return nullptr;
        }

        void UpdateAI(uint32 diff) override
        {
            if (!UpdateVictim())
                return;

            scheduler.Update(diff);
        }

    private:
        ObjectGuid _targetGUID;
        ObjectGuid _dragonGUID;
    };

    CreatureAI* GetAI(Creature* creature) const override
    {
        return new npc_dream_fogAI(creature);
    }
};

/*
 * ---
 * --- Dragonspecific scripts and handling: YSONDRE
 * ---
 */

enum YsondreNPC
{
    NPC_DEMENTED_DRUID              = 15260,
};

enum YsondreTexts
{
    SAY_YSONDRE_AGGRO               = 0,
    SAY_YSONDRE_SUMMON_DRUIDS       = 1,
};

enum YsondreSpells
{
    SPELL_LIGHTNING_WAVE            = 24819,
    SPELL_SUMMON_DRUID_SPIRITS      = 24795,
};

class boss_ysondre : public CreatureScript
{
public:
    boss_ysondre() : CreatureScript("boss_ysondre") { }

    struct boss_ysondreAI : public emerald_dragonAI
    {
        boss_ysondreAI(Creature* creature) : emerald_dragonAI(creature)
        {
        }

        void Reset() override
        {
            _stage = 1;
            emerald_dragonAI::Reset();
            events.ScheduleEvent(EVENT_LIGHTNING_WAVE, 12000);
        }

        void JustEngagedWith(Unit* who) override
        {
            Talk(SAY_YSONDRE_AGGRO);
            WorldBossAI::JustEngagedWith(who);
        }

        // Summon druid spirits on 75%, 50% and 25% health
        void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override
        {
            if (me->HealthBelowPctDamaged(100 - (25 * _stage), damage))
            {
                Talk(SAY_YSONDRE_SUMMON_DRUIDS);

                auto const& attackers = me->GetThreatMgr().GetThreatList();
                uint8 attackersCount = 0;

                for (const auto attacker : attackers)
                {
                    if ((*attacker)->ToPlayer() && (*attacker)->IsAlive())
                        ++attackersCount;
                }

                uint8 amount = attackersCount < 30 ? attackersCount * 0.5f : 15;
                amount = amount < 1 ? 1 : amount;

                for (uint8 i = 0; i < amount; ++i)
                    DoCast(me, SPELL_SUMMON_DRUID_SPIRITS, true);
                ++_stage;
            }
        }

        void ExecuteEvent(uint32 eventId) override
        {
            switch (eventId)
            {
                case EVENT_LIGHTNING_WAVE:
                    DoCastVictim(SPELL_LIGHTNING_WAVE);
                    events.ScheduleEvent(EVENT_LIGHTNING_WAVE, urand(10000, 20000));
                    break;
                default:
                    emerald_dragonAI::ExecuteEvent(eventId);
                    break;
            }
        }

    private:
        uint8 _stage;
    };

    CreatureAI* GetAI(Creature* creature) const override
    {
        return new boss_ysondreAI(creature);
    }
};

/*
 * ---
 * --- Dragonspecific scripts and handling: LETHON
 * ---
 */

enum LethonTexts
{
    SAY_LETHON_AGGRO                = 0,
    SAY_LETHON_DRAW_SPIRIT          = 1,
};

enum LethonSpells
{
    SPELL_DRAW_SPIRIT               = 24811,
    SPELL_SHADOW_BOLT_WHIRL         = 24834,
    SPELL_DARK_OFFERING             = 24804,
    SPELL_SHADOW_BOLT_WHIRL1        = 24820,
    SPELL_SHADOW_BOLT_WHIRL2        = 24821,
    SPELL_SHADOW_BOLT_WHIRL3        = 24822,
    SPELL_SHADOW_BOLT_WHIRL4        = 24823,
    SPELL_SHADOW_BOLT_WHIRL5        = 24835,
    SPELL_SHADOW_BOLT_WHIRL6        = 24836,
    SPELL_SHADOW_BOLT_WHIRL7        = 24837,
    SPELL_SHADOW_BOLT_WHIRL8        = 24838,
};

enum LethonCreatures
{
    NPC_SPIRIT_SHADE                = 15261,
};

class boss_lethon : public CreatureScript
{
public:
    boss_lethon() : CreatureScript("boss_lethon") { }

    struct boss_lethonAI : public emerald_dragonAI
    {
        boss_lethonAI(Creature* creature) : emerald_dragonAI(creature)
        {
        }

        void Reset() override
        {
            _stage = 1;
            emerald_dragonAI::Reset();
            me->RemoveAurasDueToSpell(SPELL_SHADOW_BOLT_WHIRL);
        }

        void JustEngagedWith(Unit* who) override
        {
            Talk(SAY_LETHON_AGGRO);
            WorldBossAI::JustEngagedWith(who);
            DoCastSelf(SPELL_SHADOW_BOLT_WHIRL, true);
        }

        void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override
        {
            if (me->HealthBelowPctDamaged(100 - (25 * _stage), damage))
            {
                Talk(SAY_LETHON_DRAW_SPIRIT);
                DoCast(me, SPELL_DRAW_SPIRIT);
                ++_stage;
            }
        }

        void SpellHitTarget(Unit* target, SpellInfo const* spell) override
        {
            if (spell->Id == SPELL_DRAW_SPIRIT && target->GetTypeId() == TYPEID_PLAYER)
            {
                Position targetPos = target->GetPosition();
                me->SummonCreature(NPC_SPIRIT_SHADE, targetPos, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 50000);
            }
        }

    private:
        uint8 _stage;
    };

    CreatureAI* GetAI(Creature* creature) const override
    {
        return new boss_lethonAI(creature);
    }
};

class npc_spirit_shade : public CreatureScript
{
public:
    npc_spirit_shade() : CreatureScript("npc_spirit_shade") { }

    struct npc_spirit_shadeAI : public PassiveAI
    {
        npc_spirit_shadeAI(Creature* creature) : PassiveAI(creature)
        {
        }

        void IsSummonedBy(WorldObject* summoner) override
        {
            if (!summoner)
                return;

            if (summoner->GetTypeId() != TYPEID_UNIT)
            {
                return;
            }

            _summonerGuid = summoner->GetGUID();
            me->GetMotionMaster()->MoveFollow(summoner->ToUnit(), 0.0f, 0.0f);
        }

        void MovementInform(uint32 moveType, uint32 data) override
        {
            if (moveType == FOLLOW_MOTION_TYPE && data == _summonerGuid.GetCounter())
            {
                me->CastSpell((Unit*)nullptr, SPELL_DARK_OFFERING, false);
                me->DespawnOrUnsummon(1000);
            }
        }

    private:
        ObjectGuid _summonerGuid;
    };

    CreatureAI* GetAI(Creature* creature) const override
    {
        return new npc_spirit_shadeAI(creature);
    }
};

/*
 * ---
 * --- Dragonspecific scripts and handling: EMERISS
 * ---
 */

enum EmerissTexts
{
    SAY_EMERISS_AGGRO               = 0,
    SAY_EMERISS_CAST_CORRUPTION     = 1,
};

enum EmerissSpells
{
    SPELL_PUTRID_MUSHROOM           = 24904,
    SPELL_CORRUPTION_OF_EARTH       = 24910,
    SPELL_VOLATILE_INFECTION        = 24928,
};

class boss_emeriss : public CreatureScript
{
public:
    boss_emeriss() : CreatureScript("boss_emeriss") { }

    struct boss_emerissAI : public emerald_dragonAI
    {
        boss_emerissAI(Creature* creature) : emerald_dragonAI(creature)
        {
        }

        void Reset() override
        {
            _stage = 1;
            emerald_dragonAI::Reset();
            events.ScheduleEvent(EVENT_VOLATILE_INFECTION, 12000);
        }

        void KilledUnit(Unit* who) override
        {
            if (who->GetTypeId() == TYPEID_PLAYER)
            {
                who->CastSpell(who, SPELL_PUTRID_MUSHROOM, true);
            }

            emerald_dragonAI::KilledUnit(who);
        }

        void JustEngagedWith(Unit* who) override
        {
            Talk(SAY_EMERISS_AGGRO);
            WorldBossAI::JustEngagedWith(who);
        }

        void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override
        {
            if (me->HealthBelowPctDamaged(100 - (25 * _stage), damage))
            {
                Talk(SAY_EMERISS_CAST_CORRUPTION);
                DoCast(me, SPELL_CORRUPTION_OF_EARTH, true);
                ++_stage;
            }
        }

        void ExecuteEvent(uint32 eventId) override
        {
            switch (eventId)
            {
                case EVENT_VOLATILE_INFECTION:
                    DoCastVictim(SPELL_VOLATILE_INFECTION);
                    events.ScheduleEvent(EVENT_VOLATILE_INFECTION, 120000);
                    break;
                default:
                    emerald_dragonAI::ExecuteEvent(eventId);
                    break;
            }
        }

    private:
        uint8 _stage;
    };

    CreatureAI* GetAI(Creature* creature) const override
    {
        return new boss_emerissAI(creature);
    }
};

/*
 * ---
 * --- Dragonspecific scripts and handling: TAERAR
 * ---
 */

enum TaerarTexts
{
    SAY_TAERAR_AGGRO                = 0,
    SAY_TAERAR_SUMMON_SHADES        = 1,
};

enum TaerarSpells
{
    SPELL_BELLOWING_ROAR            = 22686,
    SPELL_SHADE                     = 24313,
    SPELL_SUMMON_SHADE_1            = 24841,
    SPELL_SUMMON_SHADE_2            = 24842,
    SPELL_SUMMON_SHADE_3            = 24843,
    SPELL_ARCANE_BLAST              = 24857,
};

uint32 const TaerarShadeSpells[] =
{
    SPELL_SUMMON_SHADE_1, SPELL_SUMMON_SHADE_2, SPELL_SUMMON_SHADE_3
};
class boss_taerar : public CreatureScript
{
public:
    boss_taerar() : CreatureScript("boss_taerar") { }

    struct boss_taerarAI : public emerald_dragonAI
    {
        boss_taerarAI(Creature* creature) : emerald_dragonAI(creature)
        {
        }

        void Reset() override
        {
            me->RemoveAurasDueToSpell(SPELL_SHADE);
            _stage = 1;

            _shades = 0;
            _banished = false;
            _banishedTimer = 0;

            emerald_dragonAI::Reset();
            events.ScheduleEvent(EVENT_ARCANE_BLAST, 12000);
            events.ScheduleEvent(EVENT_BELLOWING_ROAR, 30000);
        }

        void JustEngagedWith(Unit* who) override
        {
            Talk(SAY_TAERAR_AGGRO);
            emerald_dragonAI::JustEngagedWith(who);
        }

        void SummonedCreatureDies(Creature* /*summon*/, Unit*) override
        {
            --_shades;
        }

        void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override
        {
            // At 75, 50 or 25 percent health, we need to activate the shades and go "banished"
            // Note: _stage holds the amount of times they have been summoned
            if (!_banished && me->HealthBelowPctDamaged(100 - (25 * _stage), damage))
            {
                _banished = true;
                _banishedTimer = 60000;

                me->InterruptNonMeleeSpells(false);
                DoStopAttack();

                Talk(SAY_TAERAR_SUMMON_SHADES);

                uint32 count = sizeof(TaerarShadeSpells) / sizeof(uint32);
                for (uint32 i = 0; i < count; ++i)
                    DoCast(TaerarShadeSpells[i]);
                _shades += count;
                DoCast(SPELL_SHADE);
                me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_NON_ATTACKABLE);
                me->SetReactState(REACT_PASSIVE);

                ++_stage;
            }
        }

        void ExecuteEvent(uint32 eventId) override
        {
            switch (eventId)
            {
                case EVENT_ARCANE_BLAST:
                    DoCast(SPELL_ARCANE_BLAST);
                    events.ScheduleEvent(EVENT_ARCANE_BLAST, urand(7000, 12000));
                    break;
                case EVENT_BELLOWING_ROAR:
                    DoCast(SPELL_BELLOWING_ROAR);
                    events.ScheduleEvent(EVENT_BELLOWING_ROAR, urand(20000, 30000));
                    break;
                default:
                    emerald_dragonAI::ExecuteEvent(eventId);
                    break;
            }
        }

        void UpdateAI(uint32 diff) override
        {
            if (!me->IsInCombat())
                return;

            if (_banished)
            {
                // If all three shades are dead, OR it has taken too long, end the current event and get Taerar back into business
                if (_banishedTimer <= diff || !_shades)
                {
                    _banished = false;

                    me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_NON_ATTACKABLE);
                    me->RemoveAurasDueToSpell(SPELL_SHADE);
                    me->SetReactState(REACT_AGGRESSIVE);
                }
                // _banishtimer has not expired, and we still have active shades:
                else
                    _banishedTimer -= diff;

                // Update the events before we return (handled under emerald_dragonAI::UpdateAI(diff); if we're not inside this check)
                events.Update(diff);

                return;
            }
            emerald_dragonAI::UpdateAI(diff);
        }

        void JustDied(Unit* /*killer*/) override
        {
            _JustDied();
            me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_NON_ATTACKABLE);
        }

    private:
        bool   _banished;                              // used for shades activation testing
        uint32 _banishedTimer;                         // counter for banishment timeout
        uint8  _shades;                                // keep track of how many shades are dead
        uint8  _stage;                                 // check which "shade phase" we're at (75-50-25 percentage counters)
    };

    CreatureAI* GetAI(Creature* creature) const override
    {
        return new boss_taerarAI(creature);
    }
};

/*
 * --- Spell: Dream Fog
 */

class spell_dream_fog_sleep : public SpellScriptLoader
{
public:
    spell_dream_fog_sleep() : SpellScriptLoader("spell_dream_fog_sleep") { }

    class spell_dream_fog_sleep_SpellScript : public SpellScript
    {
        PrepareSpellScript(spell_dream_fog_sleep_SpellScript);

        void HandleEffect(SpellEffIndex /*effIndex*/)
        {
            if (Unit* caster = GetCaster())
            {
                if (Unit* target = GetHitUnit())
                {
                    caster->GetAI()->SetGUID(target->GetGUID(), GUID_FOG_TARGET);
                }
            }
        }

        void FilterTargets(std::list<WorldObject*>& targets)
        {
            targets.remove_if(Acore::UnitAuraCheck(true, SPELL_SLEEP));
        }

        void Register() override
        {
            OnEffectHitTarget += SpellEffectFn(spell_dream_fog_sleep_SpellScript::HandleEffect, EFFECT_0, SPELL_EFFECT_APPLY_AURA);
            OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_dream_fog_sleep_SpellScript::FilterTargets, EFFECT_0, TARGET_UNIT_DEST_AREA_ENEMY);
        }
    };

    SpellScript* GetSpellScript() const override
    {
        return new spell_dream_fog_sleep_SpellScript();
    }
};

/*
 * --- Spell: Mark of Nature
 */

class MarkOfNatureTargetSelector
{
public:
    MarkOfNatureTargetSelector() { }

    bool operator()(WorldObject* object)
    {
        // return those not tagged or already under the influence of Aura of Nature
        if (Unit* unit = object->ToUnit())
            return !(unit->HasAura(SPELL_MARK_OF_NATURE) && !unit->HasAura(SPELL_AURA_OF_NATURE));
        return true;
    }
};

class spell_shadow_bolt_whirl : public AuraScript
{
    PrepareAuraScript(spell_shadow_bolt_whirl);

    bool Validate(SpellInfo const* /*spellInfo*/) override
    {
        return ValidateSpellInfo({ SPELL_SHADOW_BOLT_WHIRL1, SPELL_SHADOW_BOLT_WHIRL2, SPELL_SHADOW_BOLT_WHIRL3, SPELL_SHADOW_BOLT_WHIRL4, SPELL_SHADOW_BOLT_WHIRL5, SPELL_SHADOW_BOLT_WHIRL6, SPELL_SHADOW_BOLT_WHIRL7, SPELL_SHADOW_BOLT_WHIRL8 });
    }

    void HandlePeriodic(AuraEffect const* aurEff)
    {
        Unit* caster = GetCaster();
        Unit* target = GetTarget();

        if (!caster || !target)
            return;
        std::array<uint32, 8> spellForTick = { SPELL_SHADOW_BOLT_WHIRL1, SPELL_SHADOW_BOLT_WHIRL2, SPELL_SHADOW_BOLT_WHIRL3, SPELL_SHADOW_BOLT_WHIRL4, SPELL_SHADOW_BOLT_WHIRL5, SPELL_SHADOW_BOLT_WHIRL6, SPELL_SHADOW_BOLT_WHIRL7, SPELL_SHADOW_BOLT_WHIRL8 };
        uint32 tick = (aurEff->GetTickNumber() + 7/*-1*/) % 8;

        // casted in left/right (but triggered spell have wide forward cone)
        float forward = target->GetOrientation();
        if (tick <= 3)
            target->SetOrientation(forward + 0.75f * M_PI - tick * M_PI / 8);       // Left
        else
            target->SetOrientation(forward - 0.75f * M_PI + (8 - tick) * M_PI / 8); // Right

        target->CastSpell(target, spellForTick[tick], true);
        target->SetOrientation(forward);
    }

    void Register() override
    {
        OnEffectPeriodic += AuraEffectPeriodicFn(spell_shadow_bolt_whirl::HandlePeriodic, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL);
    }
};
class spell_mark_of_nature : public SpellScriptLoader
{
public:
    spell_mark_of_nature() : SpellScriptLoader("spell_mark_of_nature") { }

    class spell_mark_of_nature_SpellScript : public SpellScript
    {
        PrepareSpellScript(spell_mark_of_nature_SpellScript);

        bool Validate(SpellInfo const* /*spellInfo*/) override
        {
            return ValidateSpellInfo({ SPELL_MARK_OF_NATURE, SPELL_AURA_OF_NATURE });
        }

        void FilterTargets(std::list<WorldObject*>& targets)
        {
            targets.remove_if(MarkOfNatureTargetSelector());
        }

        void HandleEffect(SpellEffIndex effIndex)
        {
            PreventHitDefaultEffect(effIndex);
            GetHitUnit()->CastSpell(GetHitUnit(), SPELL_AURA_OF_NATURE, true);
        }

        void Register() override
        {
            OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_mark_of_nature_SpellScript::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY);
            OnEffectHitTarget += SpellEffectFn(spell_mark_of_nature_SpellScript::HandleEffect, EFFECT_0, SPELL_EFFECT_APPLY_AURA);
        }
    };

    SpellScript* GetSpellScript() const override
    {
        return new spell_mark_of_nature_SpellScript();
    }
};

void AddSC_emerald_dragons()
{
    // helper NPC scripts
    new npc_dream_fog();
    new npc_spirit_shade();

    // dragons
    new boss_ysondre();
    new boss_taerar();
    new boss_emeriss();
    new boss_lethon();

    // dragon spellscripts
    new spell_dream_fog_sleep();
    new spell_mark_of_nature();
    RegisterSpellScript(spell_shadow_bolt_whirl);
};

